monitoraSom: pacote de R para detecção de sinais usando template matching

Roteiro para a oficina do I Simpósio de Física aplicada à Ecologia e Conservação

Gabriel L. M. Rosa

2025-11-05

1 Instalação e setup inicial

Seja bem-vindo(a) ao tutorial do monitoraSom! Nessa primeira etapa, vamos fazer os downloads e instalações necessárias para o uso do monitoraSom.

1.1 1. Baixar e instalar o R.

1.2 2. Baixar e instalar o Rstudio.

  • Após instalar o R, instale o Rstudio.
  • Atenção: se houver mais de uma versão do R instalada, certifique-se qual está sendo usada ao abrir o Rstudio.
  • link para download: https://posit.co/download/rstudio-desktop/

1.3 3. Instalar o Rtools (passo necessário apenas para usuários do Windows)

  • Rtools é necessário para compilar pacotes do R que foram instalados a partir do GitHub.
  • Verifique se já existe alguma versão instalada. Caso exista, verifique se é adequada ao projeto. Se não for, desinstale a versão existente.
  • Neste momento, a versão mais recente é a 4.5.
  • link para download: https://cran.r-project.org/bin/windows/Rtools/

1.4 4. Criar uma nova pasta para conter os arquivos usandos pelo monitoraSom.

Para usuários do Windows, recomendamos criar uma pasta para análises na raiz do sistema, e dentro dela uma pasta para este projeto, por exemplo, “C:/Meus Projetos/monitoraSom_tutorial”. Dessa forma o caminho para os arquivos será o mesmo mesmo que os arquivos sejam movidos para outro computador.

1.5 5. Instalar o devtools para poder instalar pacotes a partir do GitHub.

Esse comando deve ser executado no Rstudio após a instalação do Rtools.

install.packages("devtools")

1.6 6. Carregar o devtools para o ambiente de trabalho.

library(devtools)

1.7 7. Instalar o monitoraSom

  • O monitoraSom depende de outros pacotes do R para funcionar adequadamente.
  • Atenção 1: A isntalação poderá solicitar a confirmação de instalação das dependências. Recomendamos que seja feita a instalação ou atualização de todas as dependências respondendo ‘1’ (“All”).
  • Atenção 2: Pode ser solictada também a resposta sobre instalação de pacotes de precisam de compilação. Nesse caso, responda ‘Yes’.
install_github("ConservaSom/monitoraSom", dependencies = TRUE)

1.8 8. Carregar o monitoraSom no ambiente de trabalho do R para testar a instalação.

library(monitoraSom)
library(dplyr)
library(ggplot2)
library(patchwork)

1.9 9. Salvar esse script no diretório de trabalho conforme descrito no passo 4.

Priorize salvar com um nome informativo e que mantenha a sequência de passos desse e dos tutoriais seguintes, por exemplo, “script_01_instalação.R”.

1.10 Checkpoint 01

Você instalou o monitoraSom e está pronto para começar a usar. Salve esse script no diretório de trabalho conforme descrito no passo 4.

1.11 10. Organizando o ambiente de trabalho.

Vamos seguir a partir deste ponto assumindo que o diretório de trabalho está organizado conforme descrito no passo 4 e que o código está salvo em um arquivo em formato de script ou notebook (Rmarkdown ou Quarto).

Vamos verificar e, se necessário, ajustar manualmente o diretório de trabalho da sessão atual.

getwd()
## [1] "/home/grosa/R_repos/2025_SimposioFisicaEcologia_OficinaSomR/monitoraSom"

Se o script estiver no diretório de trabalho correto, use o comando abaixo para definir o diretório automaticamente.

setwd(dirname(rstudioapi::getActiveDocumentContext()$path))
getwd() # Verificando se o diretório de trabalho foi definido corretamente

Se quiser definir manualmente o diretório de trabalho, use o comando abaixo.

project_path <- paste0(
    "<coloque aqui o caminho para a pasta do projeto>", "/monitoraSom"
)
setwd(project_path)
getwd() # Verificando se o diretório de trabalho foi definido corretamente

1.12 11. Povoando o diretório de trabalho com os arquivos de exemplo do monitoraSom.

Use a função set_workspace para adicionar os arquivos de exemplo do monitoraSom ao seu diretório de trabalho. Esse comando é recomendado para novos projetos. Evite executá-lo em diretórios que já contenham dados de projetos em andamento. Isso poderá resultar em perdas permanentes de dados.

set_workspace(project_path = "./", example_data = TRUE)

1.13 12. (Opcional) Limpeza de arquivos pre-carregados

Vamos apagar os arquivos de exemplo para continuar o tutorial como um novo projeto. Pule esta etapa se quiser obter os mesmos resultados do exemplo original.

file.remove(list.files("./detections/", full.names = TRUE))
file.remove(list.files("./match_grid_metadata/", full.names = TRUE))
file.remove(list.files("./match_scores/", full.names = TRUE))
file.remove(list.files("./soundscapes_metadata/", full.names = TRUE))
file.remove(list.files("./templates/", full.names = TRUE))
file.remove(list.files("./templates_metadata/", full.names = TRUE))
file.remove(list.files("./validation_outputs/", full.names = TRUE))

1.14 Checkpoint 02

Você povoou o diretório de trabalho com os arquivos de exemplo do monitoraSom e está pronto para seguir com o uso do pacote. Apague os arquivos de exemplo se quiser começar um novo projeto.

1.15 13. Usar o app de segmentação para escolher os templates.

Agora vamos usar o app de segmentação para escolher os templates. Note que como estamos somente escolhendo os templates, definimos o destino dos cortes de audio para a pasta “./templates”. Os templates são as amostras de som que desejamos usar para detecção nos passos seguintes. Podemos exportar os templates manualmente de dentro do app, mas nesse tutorial vamos usar o app somente para demarcar as ROIs, para depois exportar os cortes de audio automaticamente (ver passo 16).

No exemplo abaixo temos os arquivos de ROIs já feitos para este exemplo, mas vamos fazer um novo template contendo um canto completo e adicionar o comentário “Complete Song” para identificá-lo.

launch_segmentation_app(
    user = "User", # Nome do usuário
    project_path = "./", # Caminho para a pasta do projeto
    preset_path = "./app_presets/", # Caminho para a pasta de presets
    soundscapes_path = "./recordings/", # Caminho onde ler as gravações
    roi_tables_path = "./roi_tables/", # Caminho para onde exportar as ROIs
    cuts_path = "./templates/", # Caminho para onde exportar cortes de audio
    dyn_range = c(-102, -42), # Ajuste do contraste do espectrograma
    wl = 1024, # Ajuste do comprimento da janela do fft (parametro espectral)
    ovlp = 50, # Ajuste da sobreposição do fft (parametro espectral)
    color_scale = "greyscale 1", # Ajuste da escala de cores do espectrograma
    nav_autosave = TRUE # Auomtação de salvamento ao trocar de soundscape
)

1.16 14. Usar o app de segmentação para segmentar as soundscapes.

Agora vamos usar o app de segmentação para segmentar as soundscapes. Nessa etapa, definimos o destino dos cortes de audio para a pasta “./roi_cuts”. As tabelas de ROIs que serão produzidas nessa etapa serão usadas para avaliar as detecções e medir a poerformance de cada template. Note que como a segmentação já está completa, não será necessário marcar novas ROIs nessa etapa.

launch_segmentation_app(
    user = "User", project_path = "./", preset_path = "./app_presets/",
    soundscapes_path = "./soundscapes/", roi_tables_path = "./roi_tables/",
    dyn_range = c(-102, -42), wl = 1024, ovlp = 50,
    color_scale = "greyscale 1", visible_bp = TRUE, nav_autosave = TRUE
)

1.17 15. Preparando os templates.

Vamos importar todas as tabelas de ROIs para verificarmos se temos o que precisamos. Note que as tabelas de ROIs das soundscapes e dos templates encontram-se na mesma pasta, mas podemos discriminá-las facilmentepelo nome do arquivo.

df_rois <- fetch_rois(rois_path = "./roi_tables/")
unique(df_rois$soundscape_file) # Verificando os nomes das gravações
##  [1] "Bcu_1.wav"                        "Bcu_2.wav"                       
##  [3] "W54393S25597_20201104_170000.wav" "W54431S25613_20191102_055000.wav"
##  [5] "W54431S25613_20191102_064000.wav" "W54431S25613_20191104_154000.wav"
##  [7] "W54431S25613_20191105_060000.wav" "W54443S25620_20191105_054000.wav"
##  [9] "W54448S25622_20191101_073000.wav" "W54448S25622_20191101_125000.wav"
## [11] "W54448S25622_20191102_070000.wav" "W54448S25622_20191103_055000.wav"
## [13] "W54448S25622_20191104_171000.wav" "W54448S25623_20191102_064000.wav"

Filtrando as tabelas de ROIs para manter somente aquelas com os templates.

df_templates <- df_rois %>%
    filter(roi_comment %in% c("Substructure C", "Complete Song")) %>%
    group_by(roi_comment) %>%
    sample_n(1)
glimpse(df_templates)
## Rows: 2
## Columns: 19
## Groups: roi_comment [2]
## $ soundscape_path      <chr> "./recordings/Bcu_1.wav", "./recordings/Bcu_1.wav"
## $ soundscape_file      <chr> "Bcu_1.wav", "Bcu_1.wav"
## $ roi_path             <chr> "/home/grosa/R_repos/2025_SimposioFisicaEcologia_…
## $ roi_file             <chr> "Bcu_1_roi_User_20250226101835.csv", "Bcu_1_roi_U…
## $ roi_user             <chr> "User", "User"
## $ roi_input_timestamp  <chr> "2025-11-05 10:22:43", "2025-02-26 10:20:12"
## $ roi_label            <chr> "Basileuterus culicivorus", "Basileuterus culiciv…
## $ roi_start            <dbl> 26.07112, 15.69166
## $ roi_end              <dbl> 28.05989, 16.18801
## $ roi_min_freq         <dbl> 2.732697, 3.150896
## $ roi_max_freq         <dbl> 9.692551, 9.232058
## $ roi_type             <chr> "bird - song", "bird - song"
## $ roi_label_confidence <chr> "certain", "certain"
## $ roi_is_complete      <chr> "complete", "complete"
## $ roi_comment          <chr> "Complete Song", "Substructure C"
## $ roi_wl               <int> 1024, 1024
## $ roi_ovlp             <int> 50, 70
## $ roi_sample_rate      <int> 24000, 24000
## $ roi_pitch_shift      <int> 1, 1

1.18 16. Exportando os cortes de audio dos templates.

Para exportar os cortes de audio dos templates, usamos a função export_roi_cuts(). Note que os cortes de audio serão exportados para a pasta “./templates”.

export_roi_cuts(
    df_rois = df_templates, roi_cuts_path = "./templates/", overwrite = FALSE
)
list.files(path = "./templates/", pattern = "Bcu", full.names = TRUE)

1.19 Checkpoint 03

Aqui você deve decidir: se deseja seguir com o processo simplificado (função template_matching()) execute o passo 17, ou se deseja seguir com o processo detalhado, execute os passos 18-21.

1.20 17. Obtendo as detecções (processo simplificado)

Vamos usar o processo simplificado para obter as detecções. Esse processo é mais rápido e fácil, mas menos flexível. Ele é recomendado para projetos pequenos e para quem deseja obter rapidamente os resultados. Nesse caso vamos exportar os resultados para o arquivo “./detections/df_detecs.csv” e usar 4 núcleos para processamento paralelo, para ganhar um pouco de velocidade.

df_detecs <- template_matching(
    soundscapes_path = "./soundscapes/", # local de origem das soundscapes
    templates_path = "./templates/", # local de origem dos templates
    ncores = 4
)
## Template metadata successfully extracted
## All files are compatible and included in the matching grid.
## Template matching finished. Detections have been returned to the R session
## Template matching finished
glimpse(df_detecs)
## Rows: 792
## Columns: 21
## $ soundscape_path       <chr> "./soundscapes/W54393S25597_20201104_170000.wav"…
## $ soundscape_file       <chr> "W54393S25597_20201104_170000.wav", "W54393S2559…
## $ template_path         <chr> "./templates/Bcu_1_015.692-016.188s_03.151-09.23…
## $ template_file         <chr> "Bcu_1_015.692-016.188s_03.151-09.232kHz_1024wl_…
## $ template_name         <chr> "Bcu_1_015.692-016.188s_03.151-09.232kHz_1024wl_…
## $ template_min_freq     <dbl> 3.151, 3.151, 3.151, 3.151, 3.151, 3.151, 3.151,…
## $ template_max_freq     <dbl> 9.232, 9.232, 9.232, 9.232, 9.232, 9.232, 9.232,…
## $ template_start        <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ template_end          <dbl> 0.4963333, 0.4963333, 0.4963333, 0.4963333, 0.49…
## $ detection_start       <dbl> 0.5893122, 1.1401909, 2.6775271, 3.2796504, 3.90…
## $ detection_end         <dbl> 1.050513, 1.601392, 3.138728, 3.740851, 4.368597…
## $ detection_wl          <dbl> 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, …
## $ detection_ovlp        <dbl> 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, …
## $ detection_sample_rate <int> 24000, 24000, 24000, 24000, 24000, 24000, 24000,…
## $ detection_buffer      <int> 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, …
## $ detection_min_score   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ detection_min_quant   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ detection_top_n       <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ peak_index            <dbl> 65, 108, 228, 275, 324, 377, 421, 472, 519, 603,…
## $ peak_score            <dbl> 0.2584335, 0.2499991, 0.2765454, 0.2895299, 0.29…
## $ peak_quant            <dbl> 0.916, 0.874, 0.978, 0.994, 0.997, 0.984, 0.948,…

1.21 18. Processando os metadados das soundscapes (processo detalhado).

No processo detalhado, precisamos reunir os metadados das soundscapes e dos templates separadamente. Vamos começar pelas soundscapes.

df_soundscapes <- fetch_soundscape_metadata(
    soundscapes_path = "./soundscapes", # caminho para as soundscapes
    recursive = TRUE, # se TRUE, lê subdiretórios de forma recursiva
    ncores = 4 # quantidade de núcleos para processamento paralelo
)
glimpse(df_soundscapes)
## Rows: 12
## Columns: 6
## $ soundscape_path        <chr> "./soundscapes/W54393S25597_20201104_170000.wav…
## $ soundscape_file        <chr> "W54393S25597_20201104_170000.wav", "W54431S256…
## $ soundscape_duration    <dbl> 59.99454, 59.99454, 59.99454, 59.99454, 59.9945…
## $ soundscape_sample_rate <int> 24000, 24000, 24000, 24000, 24000, 24000, 24000…
## $ soundscape_bitrate     <int> 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16
## $ soundscape_layout      <chr> "mono", "mono", "mono", "mono", "mono", "mono",…

Visualizando rapidamente os espectrogramas das soundscapes para verificar se os arquivos foram importados corretamente.

## Loading required namespace: fftw
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## ℹ The deprecated feature was likely used in the monitoraSom package.
##   Please report the issue at
##   <https://github.com/ConservaSom/monitoraSom/issues>.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

1.22 19. Processando os metadados dos templates (processo detalhado).

Apesar de já termos importando os templates no passo 15, vamos fazer isso novamente para garantir que ops metadados estão corretos.

df_templates <- fetch_template_metadata(
    templates_path = "./templates/", recursive = TRUE
)
## Template metadata successfully extracted
glimpse(df_templates)
## Rows: 2
## Columns: 11
## $ template_path        <chr> "./templates/Bcu_1_015.692-016.188s_03.151-09.232…
## $ template_file        <chr> "Bcu_1_015.692-016.188s_03.151-09.232kHz_1024wl_7…
## $ template_name        <chr> "Bcu_1_015.692-016.188s_03.151-09.232kHz_1024wl_7…
## $ template_label       <chr> "Basileuterus culicivorus", "Basileuterus culiciv…
## $ template_start       <dbl> 0, 0
## $ template_end         <dbl> 0.4963333, 1.9887500
## $ template_sample_rate <int> 24000, 24000
## $ template_min_freq    <dbl> 3.151, 2.733
## $ template_max_freq    <dbl> 9.232, 9.693
## $ template_wl          <dbl> 1024, 1024
## $ template_ovlp        <dbl> 70, 50

1.23 20. Juntando os metadados das soundscapes e dos templates (processo detalhado).

O template matching pode ser custoso para grandes quantidades de arquivos. O processo detalhado permite filtrar e organizar os dados antes do processamento total. A função fetch_match_grid() combina os metadados das soundscapes e templates e checa incompatibilidades, como taxas de amostragem diferentes.

df_grid <- fetch_match_grid(
    soundscape_data = df_soundscapes, template_data = df_templates
)
## All files are compatible and included in the matching grid.
glimpse(df_grid)
## Rows: 24
## Columns: 17
## $ soundscape_path        <chr> "./soundscapes/W54393S25597_20201104_170000.wav…
## $ soundscape_file        <chr> "W54393S25597_20201104_170000.wav", "W54393S255…
## $ soundscape_duration    <dbl> 59.99454, 59.99454, 59.99454, 59.99454, 59.9945…
## $ soundscape_sample_rate <int> 24000, 24000, 24000, 24000, 24000, 24000, 24000…
## $ soundscape_bitrate     <int> 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,…
## $ soundscape_layout      <chr> "mono", "mono", "mono", "mono", "mono", "mono",…
## $ template_path          <chr> "./templates/Bcu_1_015.692-016.188s_03.151-09.2…
## $ template_file          <chr> "Bcu_1_015.692-016.188s_03.151-09.232kHz_1024wl…
## $ template_name          <chr> "Bcu_1_015.692-016.188s_03.151-09.232kHz_1024wl…
## $ template_label         <chr> "Basileuterus culicivorus", "Basileuterus culic…
## $ template_start         <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ template_end           <dbl> 0.4963333, 1.9887500, 0.4963333, 1.9887500, 0.4…
## $ template_sample_rate   <int> 24000, 24000, 24000, 24000, 24000, 24000, 24000…
## $ template_min_freq      <dbl> 3.151, 2.733, 3.151, 2.733, 3.151, 2.733, 3.151…
## $ template_max_freq      <dbl> 9.232, 9.693, 9.232, 9.693, 9.232, 9.693, 9.232…
## $ template_wl            <dbl> 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,…
## $ template_ovlp          <dbl> 70, 50, 70, 50, 70, 50, 70, 50, 70, 50, 70, 50,…

1.24 21. Obtendo as detecções (processo detalhado)

Agora vamos rodar o template matching usando a grade produzida acima como referência. Abaixo mostramos como rodar o template matching usando a função run_matching() e mais alguns recursos que essa função oferece.

run_matching(
    df_grid = df_grid,
    output_file = "./detections/df_detecs.csv", # arquivo com as detecções
    autosave_action = "replace", # ação ao salvar o arquivo
    buffer_size = "template", # buffer para evitar detecções sobrepostas
    ncores = 4 # quantidade de núcleos para processamento em paralelo
)
df_detecs <- read.csv(file = "./detections/df_detecs.csv")
glimpse(df_detecs)

Dentre as opções disponíveis para o argumento autosave_action, temos: - "replace": sobrescreve o arquivo existente. - "append": adiciona os resultados ao arquivo existente, mas deve ser usado com cuidado para evitar detecções duplicadas ao importar o arquivo.

Dentre as opções disponíveis para o argumento buffer_size, temos: - "template": buffer para evitar detecções sobrepostas em uma região de mesma duração do template. - Valores inteiros: define a quantidade de frames para o buffer. - 0: desliga o buffer, permitindo detecções sobrepostas em uma região de mesma duração do template. Essa opção é recomendada somente para projetos em que a validação não será realizada manualmente, já que retém muitas detecções redundantes.

Outras opções de filtragem disponíveis são: - min_score: define o score mínimo para detecção. Essa opção determina um limiar mínimo para a detecção, excluindo todas as detecções com scores inferiores ao valor especificado. Deve ser usada com cuidadado, pois pode eliminar completamente soundscapes e templates sem scores acima do limiar especificado, e assim eliminar a possibilidade de contagem correta de FN (False Negatives) para validação a priori (ver passo 23). - min_quant: define o quantil mínimo para detecção. Essa opção avalia o quantil dentro de cada rodada de busca, excluindo todas as detecções com scores inferiores ao quantil especificado. Como o quantil é uma medida relativa, retornará sempre ao menos uma detecção para cada rodada de busca. - top_n: define de forma explícita a quantidade de detecções com os scores mais altos a serem retidas. Essa opção retornará sempre a quantidade de detecções especificada, independente do valor máximo e mínimo de scores obtidos.

1.25 22. Caso especial: Obtendo os scores brutos.

Vamos usar a função run_matching() para fazer as buscas na grade definida, exatamente como no passo 21, mas vamos adicionar o argumento output = "scores" para obter os scores brutos ao invés de ir direto para as detecções. Note que mudamos dois argumentos: output e output_file. output define o tipo de resultado que será retornado e output_file define o caminho para o arquivo de saída, que agora será um arquivo RDS em vez de CSV.

run_matching(
    df_grid = df_grid,
    output = "scores", # arquivo com os scores brutos
    output_file = "./detections/df_scores.rds", # arquivo com os scores brutos
    ncores = 4 # quantidade de núcleos para processamento em paralelo
)
## Template matching finished. Raw scores have been saved to ./detections/df_scores.rds
glimpse(df_scores)
## Rows: 72
## Columns: 20
## $ soundscape_path        <chr> "./soundscapes//W54393S25597_20201104_170000.wa…
## $ soundscape_file        <chr> "W54393S25597_20201104_170000.wav", "W54393S255…
## $ soundscape_duration    <dbl> 59.99454, 59.99454, 59.99454, 59.99454, 59.9945…
## $ soundscape_sample_rate <int> 24000, 24000, 24000, 24000, 24000, 24000, 24000…
## $ soundscape_bitrate     <int> 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,…
## $ soundscape_layout      <chr> "mono", "mono", "mono", "mono", "mono", "mono",…
## $ template_path          <chr> "./templates//Bcu_1_004.236-004.742s_03.151-09.…
## $ template_file          <chr> "Bcu_1_004.236-004.742s_03.151-09.201kHz_1024wl…
## $ template_name          <chr> "Bcu_1_004.236-004.742s_03.151-09.201kHz_1024wl…
## $ template_label         <chr> "Basileuterus culicivorus", "Basileuterus culic…
## $ template_start         <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ template_end           <dbl> 0.5053750, 0.4963333, 0.5098750, 0.3642500, 0.4…
## $ template_sample_rate   <int> 24000, 24000, 24000, 24000, 24000, 24000, 24000…
## $ template_min_freq      <dbl> 3.151, 3.151, 3.057, 2.900, 2.975, 2.878, 3.151…
## $ template_max_freq      <dbl> 9.201, 9.232, 9.232, 9.764, 9.554, 9.636, 9.201…
## $ template_wl            <dbl> 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,…
## $ template_ovlp          <dbl> 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,…
## $ score_sliding_window   <int> 37, 36, 37, 26, 28, 28, 37, 36, 37, 26, 28, 28,…
## $ score_method           <chr> "cor", "cor", "cor", "cor", "cor", "cor", "cor"…
## $ score_vec              <list> [<data.frame[4684 x 2]>], [<data.frame[4684 x …

E importamos o arquivo de scores de volta para o ambiente de trabalho usando a função readRDS() seguindo o mesmo caminho do arquivo de saída. Ao inspecionar o objeto importado, vemos que ele retém todas as informações da grade de busca, mas agora com os scores brutos para cada combinação de template e soundscape armazenados como elementos de uma lista no lugar da coluna score_vec.

df_scores <- readRDS(file = "./detections/df_scores.rds")
glimpse(df_scores)

Para entender melhor como os scores brutos são armazenados, vamos inspecionar o primeiro elemento da lista depositada na coluna score_vec. Note que one em um data frame normal seria um valor único, temos outro data frame composto por 2 variáveis, time_vec, contendo os valores de tempo em segundos para cada frame do espectrograma da soundscape, e score_vec, contendo os scores brutos computados para cada um desses frames.

glimpse(df_scores$score_vec[[20]])
## Rows: 4,684
## Columns: 2
## $ time_vec  <dbl> 0.00000000, 0.01281113, 0.02562227, 0.03843340, 0.05124454, …
## $ score_vec <dbl> -0.1029414, -0.1029414, -0.1029414, -0.1029414, -0.1029414, …

1.26 23. Visualizando os scores brutos.

Vamos visualizar os scores brutos para poder entender melhor como as detecções foram produzidas. Notamos nessa primeira versão que precisamos de alguns ajustes para entender melhor o que está acontecendo.

plot_scores(df_scores_i = df_scores[7, ])

Agora a mesma visualização, mas com os scores ajustados para melhor visualização.

plot_scores(
    df_scores_i = df_scores[7, ], ovlp = 90, wl = 1024,
    dyn_range = c(-96, -48), color_scale = "inferno"
)

Mais um pouco de zoom agora para ver o pico correspondente a uma detecção mais promissora.

plot_scores(
    df_scores_i = df_scores[7, ], ovlp = 70, wl = 1024,
    dyn_range = c(-96, -48), color_scale = "inferno", zoom_time = c(5, 10)
)

Como as camadas de filtragem demonstradas no passo 21 ainda não foram aplicadas nesse caso, podemos experimentar as consequencias de aplica-las diretamente sobre os scores brutos nos proprios argumentos da função plot_scores(). Para isso vamos remover o zoom para ver o que está acontecendo em toda a extensão da soundscape. Nesse primeiro exemplo vamos deixar o buffer ativo e reter as 10 detecções com os scores mais altos.

plot_scores(
    df_scores_i = df_scores[7, ], ovlp = 70, wl = 1024,
    dyn_range = c(-96, -48), color_scale = "inferno",
    buffer_size = "template", top_n = 6
)

Agora vamos desligar o buffer e reter somente as detecções com scores acima de 0.1. Podemos ver que nesse caso o limiar de 0.1 ainda não foi suficiente para eliminar todas as detecções que conseguimos ver que são claramente falsas.

plot_scores(
    df_scores_i = df_scores[7, ], ovlp = 70, wl = 1024,
    dyn_range = c(-96, -48), color_scale = "inferno",
    buffer_size = 0, min_score = 0.1
)

Mas é importante lembrar que os settings selecionados aqui não funcionaão para todas as soundscapes…

plot_scores(
    df_scores_i = df_scores[11, ], ovlp = 70, wl = 1024,
    dyn_range = c(-96, -48), color_scale = "inferno",
    buffer_size = "template", min_score = 0.1
)

A avaliação visual ajuda no ajuste inicial, mas não basta para escolher o melhor limiar. É preciso validar as detecções para definir o corte ideal de score para todo o conjunto de soundscapes. Antes prosseguir, vamos extrair as detecções desejadas a partir do arquivo de scores brutos, mas agora retendo todas as detecções, sem aplicar nenhum filtro. Note que temos 41,732 detecções no total.

df_detecs_nofilt <- fetch_score_peaks(
    df_scores = df_scores,
    buffer_size = 0,
    output_file = "./detections/df_detecs_nofilt.csv"
)
## Detections extracted from scores
## Detections have been exported to ./detections/df_detecs_nofilt.csv
glimpse(df_detecs_nofilt)
## Rows: 41,732
## Columns: 21
## $ soundscape_path       <chr> "./soundscapes//W54393S25597_20201104_170000.wav…
## $ soundscape_file       <chr> "W54393S25597_20201104_170000.wav", "W54393S2559…
## $ template_path         <chr> "./templates//Bcu_1_004.236-004.742s_03.151-09.2…
## $ template_file         <chr> "Bcu_1_004.236-004.742s_03.151-09.201kHz_1024wl_…
## $ template_name         <chr> "Bcu_1_004.236-004.742s_03.151-09.201kHz_1024wl_…
## $ template_min_freq     <dbl> 3.151, 3.151, 3.151, 3.151, 3.151, 3.151, 3.151,…
## $ template_max_freq     <dbl> 9.201, 9.201, 9.201, 9.201, 9.201, 9.201, 9.201,…
## $ template_start        <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ template_end          <dbl> 0.505375, 0.505375, 0.505375, 0.505375, 0.505375…
## $ detection_start       <dbl> 0.2562227, 0.3330895, 0.4740120, 0.5124454, 0.57…
## $ detection_end         <dbl> 0.7174235, 0.7942903, 0.9352128, 0.9736462, 1.03…
## $ detection_wl          <dbl> 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, …
## $ detection_ovlp        <dbl> 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, …
## $ detection_sample_rate <int> 24000, 24000, 24000, 24000, 24000, 24000, 24000,…
## $ detection_buffer      <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ detection_min_score   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ detection_min_quant   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ detection_top_n       <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ peak_index            <dbl> 39, 45, 56, 59, 64, 85, 88, 100, 108, 131, 139, …
## $ peak_score            <dbl> 0.1690317, 0.1779760, 0.2104550, 0.2101202, 0.22…
## $ peak_quant            <dbl> 0.415, 0.486, 0.746, 0.742, 0.878, 0.519, 0.521,…

1.27 Checkpoint 04

Você obteve as detecções, mas veja que ainda não sabe quais são verdadeiras ou falsas. Nesse checkpoint você poderá fazer fitragens adicionais às demonstradas no passo acima segundo os critérios do seu projeto. Nesse ponto será necessário determinar qual método de validação será usado.

1.28 23. Validando as detecções (método a priori)

O método de validação a priori é o mais completo e rápido, pois é baseado na sobreposição temporal entre as detecções e as ROIs para a classificação de cada detecção como verdadeira ou falsa. Esse método é recomendado sempre que dados de segmentação estiverem disponíveis, pois permite a contagem correta de FN (False Negatives), a partir das ROIs que não foram alcançadas por nenhuma detecção. Nesse caso vamos usar a função validate_by_overlap() para validar as detecções.

df_validated <- validate_by_overlap(
    df_detecs = df_detecs, df_rois = df_rois, validation_user = "User"
)
## All detected species have ROIs for validation
## Validation results have been returned to the R session
glimpse(df_validated)
## Rows: 847
## Columns: 45
## $ soundscape_path       <chr> "soundscapes//Bcu_1.wav", "soundscapes//Bcu_1.wa…
## $ soundscape_file       <chr> "Bcu_1.wav", "Bcu_1.wav", "Bcu_1.wav", "Bcu_1.wa…
## $ template_path         <chr> "./templates/Bcu_1_015.692-016.188s_03.151-09.23…
## $ template_file         <chr> "Bcu_1_015.692-016.188s_03.151-09.232kHz_1024wl_…
## $ template_name         <chr> "Bcu_1_015.692-016.188s_03.151-09.232kHz_1024wl_…
## $ template_min_freq     <dbl> 3.151, 3.151, 3.151, 3.151, 3.151, 3.151, 3.151,…
## $ template_max_freq     <dbl> 9.232, 9.232, 9.232, 9.232, 9.232, 9.232, 9.232,…
## $ template_start        <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ template_end          <dbl> 0.4963333, 0.4963333, 0.4963333, 0.4963333, 0.49…
## $ detection_start       <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ detection_end         <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ detection_wl          <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ detection_ovlp        <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ detection_sample_rate <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ detection_buffer      <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ detection_min_score   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ detection_min_quant   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ detection_top_n       <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ peak_index            <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ peak_score            <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ peak_quant            <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ species               <chr> "Basileuterus culicivorus", "Basileuterus culici…
## $ detection_id          <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ roi_path              <chr> "/home/grosa/R_repos/2025_SimposioFisicaEcologia…
## $ roi_file              <chr> "Bcu_1_roi_User_20250226101835.csv", "Bcu_1_roi_…
## $ roi_user              <chr> "User", "User", "User", "User", "User", "User", …
## $ roi_input_timestamp   <chr> "2025-02-26 10:19:57", "2025-02-26 10:20:12", "2…
## $ roi_label             <chr> "Basileuterus culicivorus", "Basileuterus culici…
## $ roi_start             <dbl> 4.236420, 15.691662, 27.506793, 3.794958, 15.259…
## $ roi_end               <dbl> 4.741796, 16.188014, 28.016681, 4.189475, 15.694…
## $ roi_min_freq          <dbl> 3.150896, 3.150896, 3.057099, 3.659274, 3.561569…
## $ roi_max_freq          <dbl> 9.200792, 9.232058, 9.232058, 5.629660, 5.743649…
## $ roi_type              <chr> "bird - song", "bird - song", "bird - song", "bi…
## $ roi_label_confidence  <chr> "certain", "certain", "certain", "certain", "cer…
## $ roi_is_complete       <chr> "complete", "complete", "complete", "complete", …
## $ roi_comment           <chr> "Substructure C", "Substructure C", "Substructur…
## $ roi_wl                <int> 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, …
## $ roi_ovlp              <int> 70, 70, 70, 70, 70, 70, 70, 70, 70, 50, 50, 70, …
## $ roi_sample_rate       <int> 24000, 24000, 24000, 24000, 24000, 24000, 24000,…
## $ roi_pitch_shift       <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ roi_id                <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 1…
## $ validation_user       <chr> "User", "User", "User", "User", "User", "User", …
## $ validation_time       <chr> "2025-11-05 18:17:13", "2025-11-05 18:17:13", "2…
## $ validation            <chr> "FN", "FN", "FN", "FN", "FN", "FN", "FN", "FN", …
## $ validation_note       <chr> "no detections to intersect with", "no detection…

1.29 24. Checagens sanitárias das detecções validadas.

Inspecionando o objeto com os resultados da validação, podemos ver todas as variáveis da tabela de detecções, mas agora também agregamos dados sobre as ROIs para os casos de correspondência, revelando todos os pares de detecção x ROI, bem como as ROIs que não foram alcançadas por nenhuma detecção. O status de cada detecção é armazenado na coluna validation, que pode ter os valores TP (verdadeiro positivo ou detecção confirmada ), FP (falso positivo ou detecção falsa) ou FN (falso negativo ou detecção não alcançada). Vejamos as contagens dessa classificação para cada template.

table(df_validated$validation, df_validated$template_name)
##     
##      Bcu_1_015.692-016.188s_03.151-09.232kHz_1024wl_70ovlp_Basileuterus culicivorus.wav
##   FN                                                                                 22
##   FP                                                                                591
##   TP                                                                                 48
##     
##      Bcu_1_026.071-028.060s_02.733-09.693kHz_1024wl_50ovlp_Basileuterus culicivorus.wav
##   FN                                                                                 33
##   FP                                                                                116
##   TP                                                                                 37

Mas antes disso, vamos relembrar a quantidade de detecções que obtivemos com cada método. A versão não filtrada tem 41,732 detecções, enquanto a versão filtrada tem 4,363 detecções..

nrow(df_detecs)
## [1] 792
nrow(df_detecs_nofilt)
## [1] 41732

É importante ressaltar que tanto df_detecs, sua versão não filtrada, quanto df_validated, reunem as detecções e detecções validades de todos os templates de forma agregada, apesar dos processos serem computados separadamente para cada template.

Vamos comparar o desempenho do método de validação a priori em diferentes cenários. Ao aplicar a validação na versão filtrada das detecções, temos um processo rápido devido à menor quantidade de detecções. Já na versão não filtrada, o número de detecções é consideravelmente maior, e, ainda assim, o tempo de processamento permaneceu eficiente. Isso mostra que mesmo trabalhando com grandes volumes de dados, a validação automática ocorre de forma muito rápida. Portanto, além de permitir a validação da totalidade das detecções, a abordagem automatizada elimina a necessidade de checagem manual, sendo muito mais ágil e prática do que o método manual (a posteriori), apresentado a seguir. Por isso, nós, os desenvolvedores, recomendamos o uso do método de validação a priori sempre que possível.

# Cronometrando o tempo de processamento da validação das detecs filtradas
system.time({
    validate_by_overlap(
        df_detecs = df_detecs, df_rois = df_rois, validation_user = "User"
    )
})
## All detected species have ROIs for validation
## Validation results have been returned to the R session
##    user  system elapsed 
##   0.055   0.000   0.055
# Cronometrando o tempo de processamento da validação das detecs não filtradas
system.time({
    validate_by_overlap(
        df_detecs = df_detecs_nofilt, df_rois = df_rois, validation_user = "User"
    )
})
## All detected species have ROIs for validation
## Validation results have been returned to the R session
##    user  system elapsed 
##   0.380   0.001   0.382

1.30 25. Validando as detecções (método a posteriori)

Quando não temos dados de segmentação manual, podemos validar as detecções manualmente. Nesse caso vamos usar o app de validação manual do monitoraSom. Mas antes de prosseguir, vamos fazer uma cópia da tabela de detecções para não perdermos os dados originais.

file.copy(
    from = "./detections/df_detecs.csv",
    to = "./detections/df_detecs_aposteriori.csv",
    overwrite = FALSE # para evitar sobrescrever a cópia, caso já exista
)

Agora vamos usar o app de validação manual para validar as detecções.

launch_validation_app(
    project_path = ".", validation_user = "User",
    templates_path = "./templates/", soundscapes_path = "./soundscapes/",
    input_path = "./detections/df_detecs_aposteriori.csv",
    output_path = "./detections/df_detecs_aposteriori.csv",
    dyn_range_templ = c(-78, -30), dyn_range_detec = c(-78, -30), wl = 1024,
    ovlp = 70, time_guide_interval = 0, freq_guide_interval = 0,
    overwrite = TRUE
)

1.31 26. Importando e inspecionando os resultados da validação a posteriori.

Agora vamos importar o arquivo de detecções validadas de volta para o ambiente de trabalho usando a função read.csv() seguindo o mesmo caminho do arquivo de saída.

df_detecs_aposteriori <- read.csv(
    file = "./detections/df_detecs_aposteriori.csv"
)
glimpse(df_detecs_aposteriori)
## Rows: 732
## Columns: 21
## $ soundscape_path       <chr> "./soundscapes/W54393S25597_20201104_170000.wav"…
## $ soundscape_file       <chr> "W54393S25597_20201104_170000.wav", "W54393S2559…
## $ template_path         <chr> "./templates/Bcu_1_015.692-016.188s_03.151-09.23…
## $ template_file         <chr> "Bcu_1_015.692-016.188s_03.151-09.232kHz_1024wl_…
## $ template_name         <chr> "Bcu_1_015.692-016.188s_03.151-09.232kHz_1024wl_…
## $ template_min_freq     <dbl> 3.151, 3.151, 3.151, 3.151, 3.151, 3.151, 3.151,…
## $ template_max_freq     <dbl> 9.232, 9.232, 9.232, 9.232, 9.232, 9.232, 9.232,…
## $ template_start        <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ template_end          <dbl> 0.4963333, 0.4963333, 0.4963333, 0.4963333, 0.49…
## $ detection_start       <dbl> 0.5893122, 1.1401909, 2.6775271, 3.2796504, 3.90…
## $ detection_end         <dbl> 1.050513, 1.601392, 3.138728, 3.740851, 4.368597…
## $ detection_wl          <dbl> 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, …
## $ detection_ovlp        <dbl> 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, …
## $ detection_sample_rate <int> 24000, 24000, 24000, 24000, 24000, 24000, 24000,…
## $ detection_buffer      <int> 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, …
## $ detection_min_score   <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ detection_min_quant   <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ detection_top_n       <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ peak_index            <dbl> 65, 108, 228, 275, 324, 377, 421, 472, 519, 603,…
## $ peak_score            <dbl> 0.2584335, 0.2499991, 0.2765454, 0.2895299, 0.29…
## $ peak_quant            <dbl> 0.916, 0.874, 0.978, 0.994, 0.997, 0.984, 0.948,…

NV = not validated

table(
    df_detecs_aposteriori$validation,
    df_detecs_aposteriori$template_name
)

1.32 27. Comparando os resultados das validações a priori e a posteriori.

Vamos comparar os resultados das validações a priori e a posteriori.

df_rois %>%
    filter(!grepl("Bcu", soundscape_file)) %>%
    nrow()
nrow(df_detecs_aposteriori)
table(df_detecs_aposteriori$validation, df_detecs_aposteriori$template_name)

1.33 28. Diagnósticos de performance.

Quando trabalhamos com detecção automática, especialmente utilizando métodos como template matching, é fundamental avaliar métricas de performance como a precision (precisão) e recall ou sensitivity (sensibilidade). A precisão quantifica quantas das detecções obtidas estão corretas, enquanto o recall mede quantas das ocorrências dos sinais alvo foram de fato detectadas.

Essas métricas geralmente apresentam uma relação de toma-lá-dá-cá: aumentar o limiar para considerar detecções verdadeiras pode aumentar a precisão (menos FP), mas também pode diminuir o recall (mais FN). Por outro lado, abaixar o limiar pode aumentar o recall mas reduzir a precisão. Portanto, avaliar os resultados em termos de precision e recall é essencial para escolher o limiar ideal do template matching para seu objetivo, seja ele minimizar falsos positivos, maximizar a recuperação de eventos ou buscar um equilíbrio entre ambos.

A função diagnostic_validations() produz uma lista com os diagnósticos de performance para cada template.

ls_val_apriori <- diagnostic_validations(
    df_validated = df_validated, pos_prob = 0.90, val_a_priori = TRUE
)
## Validation diagnostics completed successfully.
# Separando os resultados para cada template
res_template1 <- ls_val_apriori[[1]]
res_template2 <- ls_val_apriori[[2]]

Inspecionando os diagnósticos de performance para cada template. Os dataframes contém todas as métricas de performance para cada template em função dos limiares de score avaliados.

res_template1$diagnostics %>% glimpse()
## Rows: 639
## Columns: 12
## $ template_name <chr> "Bcu_1_015.692-016.188s_03.151-09.232kHz_1024wl_70ovlp_B…
## $ peak_score    <dbl> 0.6027716, 0.4985262, 0.4962222, 0.4902171, 0.4830211, 0…
## $ tp            <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1…
## $ fp            <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ tn            <int> 591, 591, 591, 591, 591, 591, 591, 591, 591, 591, 591, 5…
## $ fn            <int> 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, …
## $ precision     <dbl> 1.0000000, 1.0000000, 1.0000000, 1.0000000, 1.0000000, 1…
## $ recall        <dbl> 0.01428571, 0.02857143, 0.04285714, 0.05714286, 0.071428…
## $ sensitivity   <dbl> 0.01428571, 0.02857143, 0.04285714, 0.05714286, 0.071428…
## $ specificity   <dbl> 1.000000, 1.000000, 1.000000, 1.000000, 1.000000, 1.0000…
## $ F1_score      <dbl> 0.02816901, 0.05555556, 0.08219178, 0.10810811, 0.133333…
## $ selected      <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, …
res_template2$diagnostics %>% glimpse()
## Rows: 153
## Columns: 12
## $ template_name <chr> "Bcu_1_026.071-028.060s_02.733-09.693kHz_1024wl_50ovlp_B…
## $ peak_score    <dbl> 0.5992908, 0.5977625, 0.5824681, 0.5347159, 0.5290634, 0…
## $ tp            <dbl> 1, 2, 3, 4, 4, 5, 6, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,…
## $ fp            <dbl> 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,…
## $ tn            <int> 116, 116, 116, 116, 115, 115, 115, 114, 114, 114, 114, 1…
## $ fn            <int> 69, 68, 67, 66, 66, 65, 64, 64, 63, 62, 61, 60, 59, 58, …
## $ precision     <dbl> 1.0000000, 1.0000000, 1.0000000, 1.0000000, 0.8000000, 0…
## $ recall        <dbl> 0.01428571, 0.02857143, 0.04285714, 0.05714286, 0.057142…
## $ sensitivity   <dbl> 0.01428571, 0.02857143, 0.04285714, 0.05714286, 0.057142…
## $ specificity   <dbl> 1.0000000, 1.0000000, 1.0000000, 1.0000000, 0.9913793, 0…
## $ F1_score      <dbl> 0.02816901, 0.05555556, 0.08219178, 0.10810811, 0.106666…
## $ selected      <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, …

Nos resultados de cada templates também temos disponíveis os gráficos de métricas de performance em função dos limiares de score avaliados. Na primeira coluna temos os dados do primeiro template, aquele composto somete pela parte final do canto, enquanto na segunda coluna temos os dados do segundo template, aquele composto pelo canto completo. Na primeira linha temos os gráficos dos modelos binomiais, nos quais definimos o score com a probabilidade posterior de 95% da detecção ser verdadeira. Na segunda linha mostramos os gráficos de precisão e recall em função dos limiares de score avaliados. Em todos os casos as linhas vermelhas representam os níveis de score selecionados para cada template

cowplot::plot_grid(
    res_template1$mod_plot, res_template2$mod_plot,
    res_template1$precrec_plot, res_template2$precrec_plot,
    ncol = 2
)

1.34 29. Obtendo o conjunto final de detecções.

template1_score <- ls_val_apriori[[1]]$score_cut
template2_score <- ls_val_apriori[[2]]$score_cut
template1_name <- ls_val_apriori[[1]]$diagnostics$template_name[1]
template2_name <- ls_val_apriori[[2]]$diagnostics$template_name[1]

df_detecs_final_1 <- df_detecs %>%
    filter(
        template_name == template1_name & peak_score >= template1_score
    ) %>%
    glimpse()
## Rows: 13
## Columns: 21
## $ soundscape_path       <chr> "./soundscapes/W54431S25613_20191104_154000.wav"…
## $ soundscape_file       <chr> "W54431S25613_20191104_154000.wav", "W54431S2561…
## $ template_path         <chr> "./templates/Bcu_1_015.692-016.188s_03.151-09.23…
## $ template_file         <chr> "Bcu_1_015.692-016.188s_03.151-09.232kHz_1024wl_…
## $ template_name         <chr> "Bcu_1_015.692-016.188s_03.151-09.232kHz_1024wl_…
## $ template_min_freq     <dbl> 3.151, 3.151, 3.151, 3.151, 3.151, 3.151, 3.151,…
## $ template_max_freq     <dbl> 9.232, 9.232, 9.232, 9.232, 9.232, 9.232, 9.232,…
## $ template_start        <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
## $ template_end          <dbl> 0.4963333, 0.4963333, 0.4963333, 0.4963333, 0.49…
## $ detection_start       <dbl> 7.597003, 22.739763, 33.680472, 46.465984, 10.03…
## $ detection_end         <dbl> 8.058203, 23.200964, 34.141673, 46.927185, 10.49…
## $ detection_wl          <dbl> 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, …
## $ detection_ovlp        <dbl> 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, …
## $ detection_sample_rate <int> 24000, 24000, 24000, 24000, 24000, 24000, 24000,…
## $ detection_buffer      <int> 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, …
## $ detection_min_score   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ detection_min_quant   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ detection_top_n       <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ peak_index            <dbl> 612, 1794, 2648, 3646, 802, 2297, 1983, 2349, 33…
## $ peak_score            <dbl> 0.4830211, 0.4985262, 0.4731373, 0.4962222, 0.44…
## $ peak_quant            <dbl> 0.999, 1.000, 0.999, 1.000, 1.000, 1.000, 1.000,…
df_detecs_final_2 <- df_detecs %>%
    filter(
        template_name == template2_name & peak_score >= template2_score
    ) %>%
    glimpse()
## Rows: 10
## Columns: 21
## $ soundscape_path       <chr> "./soundscapes/W54448S25622_20191101_073000.wav"…
## $ soundscape_file       <chr> "W54448S25622_20191101_073000.wav", "W54448S2562…
## $ template_path         <chr> "./templates/Bcu_1_026.071-028.060s_02.733-09.69…
## $ template_file         <chr> "Bcu_1_026.071-028.060s_02.733-09.693kHz_1024wl_…
## $ template_name         <chr> "Bcu_1_026.071-028.060s_02.733-09.693kHz_1024wl_…
## $ template_min_freq     <dbl> 2.733, 2.733, 2.733, 2.733, 2.733, 2.733, 2.733,…
## $ template_max_freq     <dbl> 9.693, 9.693, 9.693, 9.693, 9.693, 9.693, 9.693,…
## $ template_start        <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
## $ template_end          <dbl> 1.98875, 1.98875, 1.98875, 1.98875, 1.98875, 1.9…
## $ detection_start       <dbl> 8.006389, 19.407487, 28.801650, 40.437602, 43.34…
## $ detection_end         <dbl> 9.970623, 21.371721, 30.765884, 42.401836, 45.30…
## $ detection_wl          <dbl> 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, …
## $ detection_ovlp        <dbl> 50, 50, 50, 50, 50, 50, 50, 50, 50, 50
## $ detection_sample_rate <int> 24000, 24000, 24000, 24000, 24000, 24000, 24000,…
## $ detection_buffer      <int> 92, 92, 92, 92, 92, 92, 92, 92, 92, 92
## $ detection_min_score   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA
## $ detection_min_quant   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA
## $ detection_top_n       <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA
## $ peak_index            <dbl> 422, 956, 1396, 1941, 2077, 2339, 2521, 1979, 28…
## $ peak_score            <dbl> 0.5977625, 0.5992908, 0.5824681, 0.5191764, 0.53…
## $ peak_quant            <dbl> 1.000, 1.000, 0.998, 0.980, 0.989, 0.972, 0.987,…
detection_plots1 <- df_detecs_final_1 %>%
    split(., seq(nrow(.))) %>%
    purrr::map(., ~ {
        wav <- tuneR::readWave(
            filename = .x$soundscape_path, from = .x$detection_start, to = .x$detection_end, units = "seconds"
        )
        res <- fast_spectro(
            rec = wav, f = wav@samp.rate, wl = 512, ovlp = 90,
            dyn_range = c(-102, -42), color_scale = "greyscale 1",
            freq_guide_interval = 0, time_guide_interval = 0,
            zoom_freq = c(
                .x$template_min_freq,
                .x$template_max_freq
            )
        ) +
            theme_bw() +
            theme(legend.position = "none")
        return(res)
    }, .progress = TRUE)

cowplot::plot_grid(
    detection_plots1[[1]], detection_plots1[[2]],
    detection_plots1[[3]], detection_plots1[[4]],
    detection_plots1[[5]], detection_plots1[[6]],
    detection_plots1[[7]], detection_plots1[[8]],
    detection_plots1[[9]], detection_plots1[[10]],
    detection_plots1[[11]], detection_plots1[[12]],
    ncol = 4
)

detection_plots2 <- df_detecs_final_2 %>%
    split(., seq(nrow(.))) %>%
    purrr::map(., ~ {
        wav <- tuneR::readWave(
            filename = .x$soundscape_path, from = .x$detection_start, to = .x$detection_end, units = "seconds"
        )
        res <- fast_spectro(
            rec = wav, f = wav@samp.rate, wl = 1024, ovlp = 70,
            dyn_range = c(-102, -42), color_scale = "greyscale 1",
            freq_guide_interval = 0, time_guide_interval = 0,
            zoom_freq = c(
                .x$template_min_freq,
                .x$template_max_freq
            )
        ) +
            theme_bw() +
            theme(legend.position = "none")
        return(res)
    }, .progress = TRUE)

cowplot::plot_grid(
    detection_plots2[[1]], detection_plots2[[2]],
    detection_plots2[[3]], detection_plots2[[4]],
    detection_plots2[[5]], detection_plots2[[6]],
    detection_plots2[[7]], detection_plots2[[8]],
    detection_plots2[[9]], detection_plots2[[10]],
    ncol = 5
)

1.35 30. Transformando as detecções finais em tabelas de ROIs

dir.create("./final_detecs", showWarnings = FALSE)
df_final <- rbind(df_detecs_final_1, df_detecs_final_2) %>% glimpse()
df_rois_final <- detecs_to_rois(
    df_detecs = df_final, username = "User",
    output_path = "./final_detecs/"
)